package com.googlecode.cswish.struts.conversion;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.log4j.Logger;

import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PageElement;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.struts.Action;
import com.googlecode.cswish.struts.Invoker;
import com.googlecode.cswish.struts.Routing;
import com.googlecode.cswish.struts.hibernate.DynamicModel;
import com.googlecode.cswish.struts.spring.BeanIntrospector;
import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.googlecode.cswish.util.FrameConstant;
import com.googlecode.cswish.util.OgnlExUtil;
import com.googlecode.cswish.view.Elements;

public class ModelPageConverter implements ElementConverter {
	
	private static final Logger logger = Logger.getLogger(ModelPageConverter.class);
	
	@Resource
	private OgnlExUtil ognlExUtil;
	
	/**
	 * The properties come from ActionSupport
	 */
	protected static final Map<String, String> skipProperties = new HashMap<String, String>();
	static {
		// The properties come from ActionSupport
		skipProperties.put("actionErrors", "actionErrors");
		skipProperties.put("actionMessages", "actionMessages");
		skipProperties.put("errorMessages", "errorMessages");
		skipProperties.put("errors", "errors");
		skipProperties.put("fieldErrors", "fieldErrors");
		skipProperties.put("locale", "locale");
		skipProperties.put("text", "text");
		skipProperties.put("texts", "texts");
	}	
	
	@Resource
	protected ObjectFactory objectFactory;
	@Resource
	protected AutoPageConverterManager pageConverterManager;
	@Resource
	protected BeanIntrospector beanIntrospector;
	@Resource
	protected Routing routing;
	
	@Override
	public PageElement convert(ConversionContext context) {
		// simplify the auto conversion logic , Only convert the first 3 level model 
		if (context.getLevel() >= 3) {
			return null;
		}
		
		PropertyInfo propertyInfo = context.getPropertyInfo();
		String modelName = null;
		
		// Guess the model name according to class type
		Class<?> typeClass = propertyInfo.getTypeClass();
		Class<?> genericClass = propertyInfo.getGenericTypeClass();
		if (typeClass != null && typeClass != DynamicModel.class 
				&& genericClass != null && !typeClass.isAssignableFrom(genericClass)) {
			modelName = propertyInfo.getType();
		} else {
			modelName = propertyInfo.getGenericType();
		}
		
		PageElements element = convertModel(modelName, context);
		
		String type = Elements.class.getName();	// elements.ftl
		// use "value" to record class type
		element.addExtraParams("classType", modelName);
		element.setShortName(propertyInfo.getName());
		element.setType(type);
		
		return element;
	}
	
	protected PageElements convertModel(String modelName, ConversionContext context) {
		Map deadCycleCheck = context.getContext();
		PageElements existed = (PageElements) deadCycleCheck.get(modelName);
		if (existed != null) {
			PageElements cloned = existed.clone();
			// reset the array attribute to initial status
			cloned.setArray(false);
			return cloned;
		}
		
		String actionOrModel = modelName;
		PageElements pageElements = new PageElements(ognlExUtil);
		
		// FIXME: convert it now? or new PageElement(newContext);
		deadCycleCheck.put(actionOrModel, pageElements);
		Invoker invoker = context.getInvoker();
		String method = invoker.methodName;
		// Convert ModelInfo to PageElements
		boolean isPreSearch = method.startsWith(FrameConstant.ACTION_PREPARE) 
			&& method.startsWith(Action.SEARCH, FrameConstant.ACTION_PREPARE.length());
		
		PropertyInfo[] sortedInfos = prepareConvertedProperty(actionOrModel, pageElements, invoker);
		
		for (PropertyInfo propertyInfo : sortedInfos) {
			if (!isPreSearch && propertyInfo.isSearchInRange() 
					&& propertyInfo.getName().endsWith("2")) {
				continue;
			}
			ConversionContext newContext = context.clone();
			newContext.setClassName(modelName);
			newContext.setPropertyInfo(propertyInfo);
			PageElement element = pageConverterManager.convert(newContext);
			if (element != null) {
				pageElements.addElement(element);
			}
		}

		return pageElements;
	}
	
	private PropertyInfo[] prepareConvertedProperty(String actionOrModel, PageElements pageElements, Invoker invoker) {
		// 2. convert all the property to JSP component
		ModelInfo modelInfo = beanIntrospector.getModelInfo(actionOrModel);
		if (modelInfo == null) {
			logger.error("Wrong model type: " + actionOrModel + ", Can't find the relevant ModelInfo");
			return new PropertyInfo[0];
		}
		
		String method = invoker.methodName;
		if (beanIntrospector.isAction(actionOrModel) >= 0) {	// it's an action
			pageElements.setAction(routing.invoker2Path(invoker));
			pageElements.setMethod(method);
			if (invoker.isInputMethod()) {
				invoker.methodName = invoker.getSubmitMethod();
				pageElements.setTargetAction(routing.invoker2Path(invoker));
			} else {
				pageElements.setTargetAction(pageElements.getAction());
			}
		} else {								// it's a model
			// do nothing
		}
		
		
		// Skip some properties come from ActionSupport:
		List<PropertyInfo> properties;
		boolean isPlat = false;
		if (isPlat) {
			properties = modelInfo.getPersistenceProperties();
		} else {
			properties = modelInfo.getInterfaceProperties();
		}
		List<PropertyInfo> propertyInfos = new ArrayList<PropertyInfo>(properties.size());
		for (PropertyInfo propertyInfo : properties) {
			if (!skipProperties.containsKey(propertyInfo.getName())) {
				propertyInfos.add(propertyInfo);
			}
		}

		PropertyInfo[] sortedInfos = new PropertyInfo[propertyInfos.size()];
		propertyInfos.toArray(sortedInfos);
		// Oder by index
		Arrays.sort(sortedInfos, new Comparator<PropertyInfo>() {
			@Override
			public int compare(PropertyInfo info1, PropertyInfo info2) {
				return info1.getTabIndex() - info2.getTabIndex();
			}
		});
		return sortedInfos;
	}
}